﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NLog;
using Skyline;
using SmartQuant;

namespace QuantBox
{
    public class TradingCalendar : TradingCalendarBase
    {
        // ReSharper disable once UnusedMember.Local
        private const int LockPort = 15346;

        public static readonly DateTime CalendarBegin;
        public static readonly DateTime CalendarEnd;
        public static readonly TradingCalendar Instance;
        public static readonly Logger Logger;

        static TradingCalendar()
        {
            CalendarBegin = new DateTime(DateTime.Today.Year - 10, 1, 1);
            CalendarEnd = new DateTime(DateTime.Today.Year + 1, 12, 31);
            Instance = new TradingCalendar();
            Logger = LogManager.GetLogger("TradingCalendar");
        }

        public DateTime TradingDay { get; private set; } = DateTime.MinValue;

        public void Init(DateTime tradingDay, bool updateCalendar = true, string host = "data.quantbox.cn")
        {
            lock (this)
            {
                if (TradingDay == tradingDay)
                {
                    return;
                }

                TradingDay = tradingDay;
                if (!updateCalendar)
                {
                    Load();
                    if (DayList.HolidayLoaded && DayList.BeginDate.Year == CalendarBegin.Year)
                    {
                        return;
                    }
                }
                UpdateDataFromRemote(host);
                Load();
            }
        }

        private static string GetTimeRangeDataFile()
        {
            return Path.Combine(Installation.ConfigDir.FullName, "quantbox", "trading_range.json");
        }

        private static string GetCalendarDataFile()
        {
            return Path.Combine(Installation.ConfigDir.FullName, "quantbox", "trading_calendar.json");
        }
        
        private static HttpClient CreateClient(string host)
        {
            var handler = new HttpClientHandler
            {
                AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate
            };
            var client = new HttpClient(handler);
            client.DefaultRequestHeaders.AcceptEncoding.Add(new StringWithQualityHeaderValue("gzip"));
            client.DefaultRequestHeaders.AcceptEncoding.Add(new StringWithQualityHeaderValue("deflate"));
            client.BaseAddress = new Uri(host, UriKind.Absolute);
            return client;
        }

        private static void DownloadFile(string host, string url, string saveFile, Action<string> checker, int maxTry = 1)
        {
            var tryCount = 0;
            while (tryCount < maxTry)
            {
                using (var http = CreateClient($"http://{host}/"))
                {
                    try
                    {
                        var cts = new CancellationTokenSource();
                        var task = http.GetAsync(url, cts.Token);
                        task.Wait(5000);
                        if (task.Status == TaskStatus.RanToCompletion)
                        {
                            var contents = task.Result.Content.ReadAsStringAsync().Result;
                            File.WriteAllText(saveFile, contents);
                            checker(saveFile);
                            return;
                        }
                        cts.Cancel();
                    }
                    catch (Exception)
                    {
                        Thread.Sleep(1000);
                    }
                }

                tryCount++;
            }
        }

        private static void UpdateDataFromRemote(string host)
        {
            const string remoteTimeRangeFile = "get_exchange_trading_time_data";
            try
            {
                var tempFile = Path.GetTempFileName();
                DownloadFile(
                    host,
                    remoteTimeRangeFile,
                    tempFile,
                    file => new TradingCalendar().LoadTimeRange(file));

                if (File.Exists(tempFile) && new FileInfo(tempFile).Length > 0)
                {
#if NETFRAMEWORK
                    GlobalWait.Run(LockPort, () => File.Copy(tempFile, GetTimeRangeDataFile(), true), 1000);
#else
                    File.Copy(tempFile, GetTimeRangeDataFile(), true);
#endif
                    File.Delete(tempFile);
                }
            }
            catch (Exception e)
            {
                Logger.Warn($"下载交易时间出现错误,{remoteTimeRangeFile},{e.Message}");
            }

            const string remoteCalendarUrl = "trading_dates?start_date={0}0101&end_date={1}1231";
            try
            {
                var tempFile = Path.GetTempFileName();
                DownloadFile(
                    host,
                    string.Format(remoteCalendarUrl, CalendarBegin.Year, CalendarEnd.Year),
                    tempFile,
                    file => new TradingCalendar().LoadCalendar(file));

                if (File.Exists(tempFile) && new FileInfo(tempFile).Length > 0)
                {
#if NETFRAMEWORK
                    GlobalWait.Run(LockPort, () => File.Copy(tempFile, GetCalendarDataFile(), true), 1000);
#else
                    File.Copy(tempFile, GetCalendarDataFile(), true);
#endif
                    File.Delete(tempFile);
                }
            }
            catch (Exception e)
            {
                Logger.Warn($"下载交易日历出现错误,{remoteCalendarUrl},{e.Message}");
            }
        }

        private TradingCalendar()
        {
            DayList = new TradingDayList(CalendarBegin, CalendarEnd);
        }

        private void LoadTimeRange(string? file = null)
        {
            file ??= GetTimeRangeDataFile();
            if (!File.Exists(file))
            {
                return;
            }
            var content = QBHelper.ReadOnlyAllText(file);
            try
            {
                var manager = new TradingTimeManager();
                manager.Load(JsonConvert.DeserializeObject<string[][]>(content));
                TimeManager = manager;
            }
            catch (Exception e)
            {
                Logger.Warn($"加载交易时间出现错误,{e.Message}");
            }
        }

        private void LoadCalendar(string? file = null)
        {
            file ??= GetCalendarDataFile();
            if (!File.Exists(file))
            {
                return;
            }

            var content = QBHelper.ReadOnlyAllText(file);

            try
            {
                var list = JToken.Parse(content);
                var firstDate = ParseDateTime(list.First!);
                var days = new TradingDayList(firstDate.AddDays(-firstDate.DayOfYear + 1), CalendarEnd);
                var current = list.First;
                while (current != null)
                {
                    var date = ParseDateTime(current);
                    days[date] = true;
                    current = current.Next;
                }

                days.SetHoliday();
                DayList = days;
            }
            catch (Exception e)
            {
                Logger.Warn($"LoadCalendar content:[{content}] error:[{e.Message}]");
            }

            static DateTime ParseDateTime(JToken current)
            {
                return DateTime.ParseExact((string)current!, "yyyyMMdd", null);
            }
        }

        private void Load()
        {
            LoadCalendar();
            LoadTimeRange();
        }

        public TradingTimeRange GetTimeRange(Instrument inst, DateTime date)
        {
            return GetTimeRange(inst.Symbol, date);
        }

        public IList<TradingTimeRange> GetTimeRanges(Instrument inst)
        {
            return GetTimeRanges(inst.Symbol);
        }

        public DateTime GetNextMarketOpenTime(Instrument inst, DateTime dateTime, bool backtestMode = false, DateTime? dateTime1 = null)
        {
            return GetNextMarketOpenTime(inst.Symbol, dateTime, backtestMode, dateTime1);
        }

        public DateTime GetNextCloseTime(Instrument inst, DateTime dateTime)
        {
            return GetNextCloseTime(inst.Symbol, dateTime);
        }
    }
}